<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
<body>

  <dom-module id="uses-underscore-template">
    <template><div>error</div></template>
  </dom-module>

  <dom-module id="uses-dom-module">
    <template><div>from-dom-module</div></template>
  </dom-module>

  <dom-module id="no-template">
    <template><div>error</div></template>
  </dom-module>

  <dom-module id="base-el">
    <template>
      <style>
        :host {
          display: block;
          color: red;
        }
        code {
          color: black;
        }
      </style>
      <!-- dummy nodes with notes be removed by subclass -->
      <i i=[[i]]></i>
      <i i=[[i]]></i>
      <i i=[[i]]></i>
      <i i=[[i]]></i>
      <i i=[[i]]></i>
      <input id="input" value="{{foo::input}}" on-custom="handleCustomEvent">
      <span>base element: <code>foo = [[foo]]</code></span>
    </template>

    <script type="module">
import { PolymerElement } from '../../polymer-element.js';
class BaseEl extends PolymerElement {
  static get is() { return 'base-el'; }
  static get properties() {
    return { foo: { type: String, value: 5 } };
  }
  constructor() {
    super();
    this.handleCustomEvent = sinon.spy();
  }
}
customElements.define(BaseEl.is, BaseEl);
window.BaseEl = BaseEl;
</script>
  </dom-module>

  <dom-module id="child-el">
    <script type="module">
class ChildEl extends window.BaseEl {
  static get is() { return 'child-el'; }
  static get properties() {
    return { bar: { type: String, value: 3 } };
  }
}
customElements.define(ChildEl.is, ChildEl);
window.ChildEl = ChildEl;
</script>
  </dom-module>

  <dom-module id="grand-child-el">
    <script type="module">
class GrandChildEl extends window.ChildEl {
  static get is() { return 'grand-child-el'; }
  static get properties() {
    return { bar: { type: String, value: 3 } };
  }
}
customElements.define(GrandChildEl.is, GrandChildEl);
window.GrandChildEl = GrandChildEl;
</script>
  </dom-module>

  <dom-module id="child-el-with-template">
    <template>should ignore this template</template>
    <script type="module">
class ChildElWithTemplate extends window.GrandChildEl {
  static get properties() {
    return { bar: { type: String, value: 3 } };
  }
  static get template() {
    var template = window.GrandChildEl.template.cloneNode(true);
    var div = document.createElement('div');
    div.textContent = 'child';
    template.content.appendChild(div);
    // Move input to the end
    template.content.appendChild(template.content.querySelector('#input'));
    // Remove all the <i>'s, such that the total number of nodes in
    // this template is less than the super
    Array.from(template.content.querySelectorAll('i'))
      .forEach(i=>i.parentNode.removeChild(i));
    return template;
  }
}
customElements.define('child-el-with-template', ChildElWithTemplate);
window.ChildElWithTemplate = ChildElWithTemplate;
</script>
  </dom-module>

  <dom-module id="parent-element-template-overriding">
    <script type="module">
import { html } from '../../lib/utils/html-tag.js';
import { PolymerElement } from '../../polymer-element.js';
class ParentElementWithTemplate extends PolymerElement {
  static get template() {
    return html`This template should not exist`;
  }
}
class ChildWithNoTemplate extends ParentElementWithTemplate {
  static get template() {
    return null;
  }
}
customElements.define('child-with-no-template', ChildWithNoTemplate);
</script>
  </dom-module>

  <test-fixture id="basic">
    <template>
      <base-el></base-el>
      <child-el></child-el>
    </template>
  </test-fixture>

  <test-fixture id="basic-with-attributes">
    <template>
      <base-el></base-el>
      <child-el foo="7" bar="7"></child-el>
    </template>
  </test-fixture>

  <test-fixture id="with-template">
    <template>
      <base-el></base-el>
      <child-el-with-template></child-el-with-template>
    </template>
  </test-fixture>

  <script type="module">
import { html } from '../../lib/utils/html-tag.js';
import { Polymer } from '../../polymer-legacy.js';

suite('ChildElement extends BaseElement', function() {
  test('child has base properties', function() {
    var f = fixture('basic');
    var child = f[1];
    assert.equal(child.foo, 5);
    assert.equal(child.bar, 3);
  });

  test('child can change base properties', function() {
    var f  = fixture('basic-with-attributes');
    var child = f[1];
    assert.equal(child.foo, 7);
    assert.equal(child.bar, 7);
  });

  test('child has base template and style', function() {
    var f = fixture('basic');
    var base = f[0];
    var child = f[1];

    // Child template is the same as the base template.
    assert.equal(child.shadowRoot.childNodes.length, child.shadowRoot.childNodes.length);
    for (var i=0; i < child.shadowRoot.childNodes.length; i++) {
      var childEl = child.shadowRoot.childNodes[i];
      var baseEl = child.shadowRoot.childNodes[i];
      assert.equal(childEl.innerHTML, baseEl.innerHTML);
    }

    // And it's something that we expect.
    var code = child.shadowRoot.querySelector('code');
    assert.equal(code.innerHTML, 'foo = 5');

    // And the base style is the same.
    assert.equal(getComputedStyle(base).color, getComputedStyle(child).color);

    // Id map works as expected
    assert.equal(child.$.input.localName, 'input');
    assert.equal(child.$.input.id, 'input');

    // 2-way bindings work as expected
    child.$.input.value = 'changed';
    child.$.input.dispatchEvent(new CustomEvent('input'));
    assert.equal(child.foo, 'changed');

    // Declarative event listeners work as expected
    assert.equal(child.handleCustomEvent.callCount, 0);
    child.$.input.dispatchEvent(new CustomEvent('custom'));
    assert.equal(child.handleCustomEvent.callCount, 1);
  });

  test('child with properties has updated base template', function() {
    var f = fixture('basic-with-attributes');
    var base = f[0];
    var child = f[1];

    // Child template is not the same as the base template.
    assert.notEqual(child.shadowRoot.innerHTML, base.shadowRoot.innerHTML);

    // And it's something that we expect.
    var code = child.shadowRoot.querySelector('code');
    assert.equal(code.innerHTML, 'foo = 7');
  });
});

suite('ChildElement extends BaseElement and the template', function() {
  test('child has base properties', function() {
    var f = fixture('with-template');
    var child = f[1];
    assert.equal(child.foo, 5);
    assert.equal(child.bar, 3);
  });

  test('child has derived template and style', function() {
    var f = fixture('with-template');
    var base = f[0];
    var child = f[1];

    // Child template is not the same as the base template.
    assert.notEqual(child.shadowRoot.innerHTML, base.shadowRoot.innerHTML);

    // And it's something that we expect.
    assert.equal(child.shadowRoot.querySelector('code').innerHTML, 'foo = 5');
    assert.equal(child.shadowRoot.querySelector('div').innerHTML, 'child');

    // And the base style is the same.
    assert.equal(getComputedStyle(base).color, getComputedStyle(child).color);

    // Id map works as expected
    assert.equal(child.$.input.localName, 'input');
    assert.equal(child.$.input.id, 'input');

    // 2-way bindings work as expected
    child.$.input.value = 'changed';
    child.$.input.dispatchEvent(new CustomEvent('input'));
    assert.equal(child.foo, 'changed');

    // Declarative event listeners work as expected
    assert.equal(child.handleCustomEvent.callCount, 0);
    child.$.input.dispatchEvent(new CustomEvent('custom'));
    assert.equal(child.handleCustomEvent.callCount, 1);
  });
});

suite('child overriding a template', function() {
  let el;

  setup(function() {
    el = document.createElement('child-with-no-template');
    document.body.appendChild(el);
  });

  teardown(function() {
    document.body.removeChild(el);
  });

  test('returning null nullifies the parent template', function() {
    assert.equal(el.shadowRoot, null);
  });
});

suite('Legacy _template property: null/undefined', function() {

  let el;
  teardown(function() {
    if (el) {
      document.body.removeChild(el);
    }
  });

  test('_template: returning template works', function() {
    Polymer({
      is: 'uses-underscore-template',
      _template:  html`<div>from-underscore-template</div>`
    });
    const el = document.createElement('uses-underscore-template');
    document.body.appendChild(el);
    assert.equal(el.shadowRoot.firstChild.textContent, 'from-underscore-template');
  });

  test('_template: undefined falls back to dom-module', function() {
    Polymer({
      is: 'uses-dom-module',
      _template: undefined
    });
    const el = document.createElement('uses-dom-module');
    document.body.appendChild(el);
    assert.equal(el.shadowRoot.firstChild.textContent, 'from-dom-module');
  });

  test('_template: null overrides dom-module', function() {
    Polymer({
      is: 'no-template',
      _template: null
    });
    const el = document.createElement('no-template');
    document.body.appendChild(el);
    assert.notOk(el.shadowRoot);
  });

  test('_template: returning template works in sub-class', function() {
    customElements.define('sub-uses-underscore-template', class extends customElements.get('uses-underscore-template') {
      get _template() { return this._$tmpl || html`<div>sub-from-underscore-template</div>`; }
      set _template(v) { this._$tmpl = v; }
    });
    const el = document.createElement('sub-uses-underscore-template');
    document.body.appendChild(el);
    assert.equal(el.shadowRoot.firstChild.textContent, 'sub-from-underscore-template');
  });

  test('_template: undefined falls back to dom-module in sub-class', function() {
    customElements.define('sub-uses-dom-module', class extends customElements.get('uses-dom-module') {
      get _template() { return this._$tmpl || undefined; }
      set _template(v) { this._$tmpl = v; }
    });
    const el = document.createElement('sub-uses-dom-module');
    document.body.appendChild(el);
    assert.equal(el.shadowRoot.firstChild.textContent, 'from-dom-module');
  });

  test('_template: null overrides dom-module in sub-class', function() {
    customElements.define('sub-no-template', class extends customElements.get('no-template') {
      get _template() { return this._$tmpl || null; }
      set _template(v) { this._$tmpl = v; }
    });
    const el = document.createElement('sub-no-template');
    document.body.appendChild(el);
    assert.notOk(el.shadowRoot);
  });

});
</script>
</body>
</html>
